# coding:utf-8
import binascii

import os

import base64
import json
import logging

import time
from datetime import date, timedelta

from io import BytesIO

from PIL import Image
from flask import request, current_app
from flask_restful import Api, Resource, abort
from sqlalchemy import or_, and_

from app.api.constant import LOCKED_PAID_BID_POINT, EnterpriseDispatchQueryType
from app.api.demand_schemas import enterprise_dispatch_in_schema, enterprise_dispatch_out_schema, \
    enterprise_dispatch_export_schema
from app.auth.constant import PROTOCOL_VERSION_KEY
from app.enterprise.constant import EnterpriseLevel
from app.enterprise.models import Enterprise
from app.op.utils import get_demand_price
from common.demand_util import demand_bid_count
from .. import db
from app.api.supplier_codes import (
    DEMAND_NOT_EXIST, NOT_ENGINEER, NO_PARTIC,
    FEEDBACK_DUPLICATE, NOT_WIN_DEMAND, USER_BLACK, DEMAND_NOT_ONGOING,
    BID_ALREADY, NO_PACK, OUT_OF_PACK, BID_LOWER_THAN_START,
    BID_LOWER, NOT_MATCH_FREE_OFFER, NOT_MATCH_SECOND_OFFER,
    MUST_BE_KEEP, LOW_BID_POINT, BID_LOWER_THAN_SELF,
    ONE_DAY_DISABLE_FEEDBACK, OUT_OF_FREE_REASON_CNT,
    OUT_OF_REASON_CNT, LOCKED_CANNOT_FEEDBACK, OFFER_NOT_EQ_EXPECTED,
    USER_NOT_BRONZE, USER_BRONZE_INVALID, DISPATCH_CANNOT_FOLLOW,
    DISPATCH_ALREADY_BID_BY_COLLEAGUE, DEMAND_LOCKED, APPLY_REAL_NUM_ALREADY, GIVE_UP_CANNOT_APPLY_REAL_NUM,
    VOUCHER_PARTIC_CANNOT_FEEDBACK, DEMAND_DONE, NOT_ENTERPRISE_USER, CAN_NOT_GET_BEFORE,
)
from app.api_v2.codes import IMAGE_TYPE_ERROR
from app.api_v2.demand_schemas import (
    feedback_schema, demand_finish_img_schema, demand_bid_schema
)
from app.models import get_timestamp
from app.token import AuthToken
from app.constant import HttpCode
from app.axb.models import VirtualNumber, QueryCallNumberRecord
from app.op.models import (
    Area, UserTag, Demand, Partic, DemandFinishImg, BidCostRecord,
    UserInfo, UserVisitDemand,
    UserProtocol, User, RenewableDemand, Voucher)
from app.op.constant import (
    AreaType, DemandStatus, UserLevel, BusinessToKeys,
    UserType, FeedbackStatus, ParticStatus, DemandTag,
    MAX_OFFER, FollowType, BidCostType, MONTHLY_REFUSE_MAX, UserVisitDemandChannel,
    ApplyRealNumberState, ContactState, OperatedStatus, UserCreditRule)
from common.models import Feedback
from app.api_v2.views import Response
from app.api.codes import SUCCESS, PARAMS_ILLEGAL
from app.api.utils import (
    generate_demand_list_json, generate_demand_info_json,
    RandomDemandCount, calc_bronze_bid_point
)
from common.constant import PARTIC_CACHE_KEY, PARTIC_CACHE_TIMEOUT, TAIWAN_CODE
from app.api_v2.views import AppAuthResource

api = Api()


@api.resource('/demands')
class DemandsResource(Resource):
    """
    created by chenhj at 2017/11/21

    """

    def get(self):
        data = request.args or {}

        sort = data.get('sort', 'published_at')
        order = data.get('order', 'desc')
        pagesize = int(data.get('pagesize', 20))
        sort_val = data.get('sort_val', None)

        # city参数: all代表全国, mine代表我关注的地域范围,
        # 除前边2个之外的string代表取某个城市数据, 默认是我关注的地域数据
        # 若city为all, 则不需要筛选area_id即可
        area_ids = None
        city = data.get('city', 'mine')

        token = request.headers.get('token')
        # 如果是工程商登录, 则参与状态改为该工程商对此项目的参与状态
        user = AuthToken.verify_app_token(token) if token else None
        if city == 'mine':
            # 我关注的
            if not user:
                city = 'all'
            else:
                # area_ids = user.pack.area_id if user.pack else list()
                area_ids = [i.id for i in user.attention_areas] if user.pack else list()
        if city not in ('all', 'mine'):
            # 特定城市, 若无, 则取特定省份
            c = Area.get_by_args(name=city, type=AreaType.CITY.value)
            area_ids = [c.id] if c else Area.get_city_ids(city)

        # business参数:
        #  all代表全部类型, mine代表我关注的业务, id代表某个特定业务, 默认是我关注的业务
        business = list()
        business_type = data.get('business', 'mine')
        if business_type == 'mine':
            if not user:
                business_type = 'all'
            else:
                user_tag = UserTag.get_by_args(user_id=user.id)
                for cate, field in BusinessToKeys.items():
                    if getattr(user_tag, field, None):
                        business.append(cate)
        if business_type not in ('all', 'mine'):
            business.append(business_type)
        # demand参数:
        # all 代表全部项目,        mine 代表我参与的项目,
        # ongoing 代表进行中的项目, free 表示免费项目, 默认是全部项目
        mine_demand = False
        status = [
            DemandStatus.ONGOING.value, DemandStatus.COMPETE_DONE.value,
            DemandStatus.DONE.value, DemandStatus.BID_DONE.value,
            DemandStatus.SECOND_ONGOING.value, DemandStatus.FREE_ONGOING.value
        ]
        demand_type = data.get('demand', 'all')
        if demand_type == 'ongoing':
            status = [
                DemandStatus.ONGOING.value, DemandStatus.BID_DONE.value,
                DemandStatus.SECOND_ONGOING.value, DemandStatus.FREE_ONGOING.value
            ]
        if demand_type == 'mine':
            mine_demand = True
        # 免费项目只返回免费竞价中的项目
        if demand_type == 'free':
            status = [DemandStatus.FREE_ONGOING.value]
        else:
            # 若用户为铜牌用户及以上, 则同时返回优先抢单项目
            if (
                user
                and user.highest_level >= UserLevel.LEVEL_3.value
                and user.bronze_medal_valid
            ):
                status.append(DemandStatus.BRONZE_MEDAL_DISCOUNT.value)

        demand_query = Demand.query.filter(
            Demand.deleted_at == 0,
            Demand.status.in_(status),
        )

        if area_ids:
            demand_query = demand_query.filter(Demand.area_id.in_(area_ids))

        # 由于会出现你没关注某个区域,但是仍然派单给你的情况,所以派单的项目需要在过滤区域后再加上
        if user and user.highest_level > UserLevel.LEVEL_3.value and demand_type != 'free':
            demand_query = demand_query.union(
                Demand.query.filter(
                    Demand.deleted_at == 0,
                    Demand.id.in_(Partic.query.filter(
                        Partic.demand_status.in_([
                            DemandStatus.DISPATCHING.value,
                            DemandStatus.MASTER_DISPATCHING.value
                        ]),
                        Partic.supplier_id == user.id
                    ).with_entities(Partic.demand_id)),
                )
            )
        if business:
            demand_query = demand_query.filter(Demand.cate.in_(business))
        if mine_demand:
            if not user:
                abort(HttpCode.UNAUTHORIZED)

            # 只返回抢单成功的和正在排队的(不返回取消跟进的和排队失败的)
            # 小霞要求取消跟进的也要返回 at 2018-3-30
            demand_query = demand_query.join(
                Partic, Partic.demand_id == Demand.id
            ).filter(
                Partic.supplier_id == user.id,
                or_(
                    Partic.status == ParticStatus.SUCCEED.value,
                    and_(
                        Partic.published_count == Demand.published_count,
                        Partic.status == ParticStatus.WAITING.value,
                        Demand.status.notin_([DemandStatus.COMPETE_DONE.value, DemandStatus.DONE.value])
                    )
                )
            )
        # FIXME by chenhj at 2018/5/11 纯粹技术债务,永远过滤金牌订单(假单)
        demand_query = demand_query.filter(Demand.for_gold.__eq__(False))
        taiwan = Area.get_by_id(TAIWAN_CODE)
        if not user or taiwan not in user.attention_areas:
            demand_query = demand_query.filter(Demand.area_id != TAIWAN_CODE)
        cnt = demand_query.count()

        if sort_val:
            demand_query = demand_query.filter(
                getattr(Demand, sort) < sort_val if order == 'desc' else getattr(Demand, sort) > sort_val
            )
        # 如果是工程商登录, 则参与状态改为该工程商对此项目的参与状态
        resp = generate_demand_list_json(demand_query.order_by(
            getattr(getattr(Demand, sort), order)()
        )[:pagesize], user)
        return Response(SUCCESS, {
            'cnt': cnt,
            'list': resp
        })


@api.resource('/demand/<int:demand_id>')
class DemandDetailResource(Resource):
    """
    created by chenhj at 2017/11/21

    """
    def get(self, demand_id):
        demand = Demand.get_by_id(demand_id)
        # FIXME by chenhj at 2018/5/11 纯粹技术债务,永远过滤金牌订单(假单)
        if not demand or demand.status in (
            DemandStatus.DRAFT.value, DemandStatus.UNCHECKED.value,
            DemandStatus.INVALID.value
        ) or demand.for_gold:
            return Response(DEMAND_NOT_EXIST)

        token = request.headers.get('token')
        # 如果是工程商登录, 则参与状态改为该工程商对此项目的参与状态
        user = AuthToken.verify_app_token(token) if token else None

        # 如果是派单中的项目, 则判断登录的用户是否是派单所达的客户
        # 未登录或者不是派单用户, 都返回项目不存在
        if demand.status in (
            DemandStatus.DISPATCHING.value, DemandStatus.MASTER_DISPATCHING.value
        ):
            if not user or not Partic.query.filter(
                Partic.demand_id == demand.id,
                Partic.supplier_id == user.id,
                Partic.demand_status.in_([
                    DemandStatus.MASTER_DISPATCHING.value,
                    DemandStatus.DISPATCHING.value
                ])
            ).first():
                return Response(DEMAND_NOT_EXIST)

        # 如果是铜牌一口价订单, 则判断登录且用户是否有效的铜牌用户
        if demand.status == DemandStatus.BRONZE_MEDAL_DISCOUNT.value:
            if not user or not user.bronze_medal_valid:
                return Response(DEMAND_NOT_EXIST)

        # 记录用户已经查看过此项目
        if user:
            if user.scan_demands:
                if demand not in user.scan_demands:
                    user.scan_demands.append(demand)
            else:
                user.scan_demands = [demand]

        return Response(SUCCESS, {
            'info': generate_demand_info_json(demand, user)
        })


@api.resource('/demand/<int:demand_id>/partic')
class DemandParticResource(Resource):
    """
    created by chenhj at 2017/11/21

    """
    def get(self, demand_id):
        demand = Demand.get_by_id(demand_id)
        if not demand:
            return Response(DEMAND_NOT_EXIST)

        token = request.headers.get('token')
        # 如果是工程商登录, 则参与状态改为该工程商对此项目的参与状态
        user = AuthToken.verify_app_token(token) if token else None

        partic_supplier_list = current_app.redis.get(
            PARTIC_CACHE_KEY.format(demand_id=demand_id, demand_status=demand.status)
        ) or list()

        # 查询铜牌一口价下竞价成功信息
        bronze_partic_info = {}
        bronze_success_partic = Partic.query.filter(
            Partic.demand_id == demand.id,
            Partic.status.in_([
                ParticStatus.SUCCEED.value,
                ParticStatus.TIMEOUT_NOT_CONTACT.value,
                ParticStatus.NOT_CONTACT.value,
                ParticStatus.BALANCE_NOT_ENOUGH.value
            ]),
            Partic.demand_status == DemandStatus.BRONZE_MEDAL_DISCOUNT.value
        ).first()
        if bronze_success_partic:
            bronze_partic_info.update({
                'id': bronze_success_partic.supplier_id,
                'id_': bronze_success_partic.supplier_id,
                'nickname': bronze_success_partic.owner.nickname,
                'hide_nickname': bronze_success_partic.hide_nickname,
                'mobile': '****' + bronze_success_partic.owner.mobile[-4:],
                'follow_type': bronze_success_partic.follow_type,
                'status': bronze_success_partic.status,
                'voucher_id': bronze_success_partic.voucher_id,
                'contact_state': bronze_success_partic.contact_state,
                'contact_seconds': bronze_success_partic.contact_seconds,
                'bid_win_at': bronze_success_partic.bid_win_at,
            })

        if not partic_supplier_list:
            partics = Partic.demand_partics(demand)

            for partic in partics:
                # 未来的报价不返回(系统插入的虚拟报价可能在当前时间之后, 并过滤掉铜牌一口价数据)
                if (
                    partic.offer_at - get_timestamp() <= 0
                    and partic.demand_status != DemandStatus.BRONZE_MEDAL_DISCOUNT.value
                ):
                    partic_supplier_list.append({
                        'id': partic.supplier_id,
                        'id_': partic.supplier_id,
                        'status': partic.status,
                        'nickname': partic.owner.nickname,
                        'hide_nickname': partic.hide_nickname,
                        'mobile': '****' + partic.owner.mobile[-4:],
                        'com': partic.owner.com,
                        'avatar': partic.owner.avatar_url,
                        'online': 0,
                        'partic_at': partic.created_at,
                        'offer_at': partic.offer_at,
                        'is_breach': partic.is_breach,
                        'breach_count': partic.owner.breach_count,
                        'contact_state': partic.contact_state,
                        'contact_seconds': partic.contact_seconds,
                        'bid_win_at': partic.bid_win_at,
                        'is_robot': partic.is_robot,
                        'offer': str(partic.offer),
                        'voucher_id': partic.voucher_id
                    })
            current_app.redis.set(
                PARTIC_CACHE_KEY.format(demand_id=demand.id, demand_status=demand.status),
                json.dumps(partic_supplier_list),
                ex=PARTIC_CACHE_TIMEOUT
            )
        else:
            logging.info('partic info get from cache: [demand_id]'.format(demand_id=demand_id))
            partic_supplier_list = json.loads(partic_supplier_list)

        # 是否有人爆灯
        has_max = False
        if demand.status == DemandStatus.ONGOING.value:
            has_max = (MAX_OFFER <= demand.max_partic_offer)

        supplier_partic_status = Partic.partic_status(demand, user)

        dispatch_winner = None
        if user and user.enterprise_info:
            if user.highest_level != EnterpriseLevel.LEVEL_6.value:
                # 非地方合作伙伴, 取同企业下中标人
                enterprise_cond = (Partic.enterprise_id == user.enterprise_info)
            else:
                # 地方合作伙伴, 取所有地方合作伙伴企业下中标人
                enterprise_cond = (Partic.enterprise_level == EnterpriseLevel.LEVEL_6.value)
            # 检查企业下或所有白金企业中是否有中标人
            winner_partic = Partic.query.filter(
                Partic.demand_id == demand.id,
                Partic.demand_status.in_([
                    DemandStatus.DISPATCHING.value,
                    DemandStatus.MASTER_DISPATCHING.value
                ]),
                enterprise_cond,
                Partic.status == ParticStatus.SUCCEED.value,
            ).first()
            if winner_partic:
                dispatch_winner = {
                    'id': winner_partic.owner.id,
                    'nickname': winner_partic.owner.nickname,
                    'mobile': '****' + winner_partic.owner.mobile[-4:],
                    'contact_state': winner_partic.contact_state,
                    'contact_seconds': winner_partic.contact_seconds,
                }

        return Response(
            SUCCESS,
            {
                'demand_status': demand.status,
                'has_max': has_max,
                'partic_supplier_list': partic_supplier_list,
                'supplier_partic_status': supplier_partic_status,
                'bronze_partic_info': bronze_partic_info,
                'dispatch_winner': dispatch_winner
            }
        )


@api.resource('/demand/<int:demand_id>/feedback')
class DemandFeedbackResource(AppAuthResource):
    """
    create by ld_wu 2017-11-22
    项目信息反馈
    """

    def get(self, demand_id):
        """
        created by chenhj at 2018/4/2

        """
        user_info = UserInfo.default_user_info(self.user.id)
        partic = Partic.get_by_args(
            demand_id=demand_id,
            supplier_id=self.user.id,
        )
        enable = True
        info = ''
        cannot_touch_info = '多次拨打不影响发起退款，销售机会来之不易，请尝试不同时段多次联系客户'
        touch_enable = True
        # 与用户通话60s以上, 不允许反馈联系不到用户
        if partic.contact_seconds > 60:
            cannot_touch_info = '您已与客户通话 {:0>2}:{:0>2}, 不能提交该选项'.format(
                partic.contact_seconds // 60, partic.contact_seconds % 60
            )
            touch_enable = False
        else:
            virtual_number_data = VirtualNumber.get_by_args(
                demand_id=demand_id,
                tel_b=self.user.mobile
            )
            if virtual_number_data:
                call_data = QueryCallNumberRecord.query.filter(
                    QueryCallNumberRecord.created_at >= partic.bid_win_at,
                    QueryCallNumberRecord.caller == virtual_number_data.tel_b,
                    QueryCallNumberRecord.called == virtual_number_data.tel_x
                ).order_by(
                    QueryCallNumberRecord.created_at.desc()
                )
                # 呼叫次数未达到3次, 不允许反馈呼叫不到客户
                if call_data.count() < 3:
                    cannot_touch_info = '您呼叫客户次数未满3次, 不能提交该选项'
                    touch_enable = False
                # 第一个呼叫与最后一次呼叫之间间隔未达到15分钟, 不允许反馈呼叫不到客户
                elif call_data[0].created_at - call_data[-1].created_at <= 15 * 60:
                    cannot_touch_info = '呼叫客户时间间隔不足15分钟，不能提交该选项'
                    touch_enable = False
        reason_left_times = self.user.reason_left_times
        free_reason_feedback_cnt = user_info.free_reason_feedback_cnt
        # if free_reason_feedback_cnt <= 0 and reason_left_times <= 0:
        #     enable = False
        #     info = '您的退款机会已全部用完'

        # 超过1天不允许发起反馈
        if get_timestamp() - partic.bid_win_at >= 60 * 60 * 24:
            enable = False
            info = '超过24小时, 不支持退款'
        # 拨打前获取真实号码不允许发起反馈
        if partic.apply_real_number_state == ApplyRealNumberState.GET_BEFORE.value:
            enable = False
        # FIXME by chenhj at 2018/4/25 派单不允许退单
        if partic.demand_status in (
            DemandStatus.DISPATCHING.value, DemandStatus.MASTER_DISPATCHING.value
        ):
            enable = False
        # 代金券支付不允许发起反馈
        if partic.voucher_id != 0:
            enable = False
        return Response(body={
            'locked_paid_bid_point': LOCKED_PAID_BID_POINT,
            'enable': enable,
            'info': info,
            'free_reason_left_times': free_reason_feedback_cnt,
            'free_reason_value': Feedback.NO_REASON.value,
            'free_reason_info': '' if free_reason_feedback_cnt > 0 else '剩余无理由退款0次, 无法使用',
            'reason_left_times': reason_left_times,
            'reason_info': '' if reason_left_times > 0 else '本月退款已被驳回{}次, 无法使用'.format(MONTHLY_REFUSE_MAX),
            'feedback_contact_user': {
                'nickname': '周经理',
                'contact': 'WPHHZ99',
                'type': '微信号'
            },
            'reason_options': [
                {
                    'option': Feedback.NO_DEMAND.display,
                    'value': Feedback.NO_DEMAND.value,
                    'enable': True,
                    'info': '及时联系、积极营销、阐述优势，方能打动客户。'
                }, {
                    'option': Feedback.CANNOT_TOUCH.display,
                    'value': Feedback.CANNOT_TOUCH.value,
                    'enable': touch_enable,
                    'info': cannot_touch_info
                }, {
                    'option': Feedback.DESC_WRONG.display,
                    'value': Feedback.DESC_WRONG.value,
                    'enable': True,
                    'info': '个人原因看错信息抢错单不予退款。小小的误会也许并不影响你们的缘分。'
                }, {
                    'option': Feedback.OTHER.display,
                    'value': Feedback.OTHER.value,
                    'enable': True,
                    'info': ''
                }
            ]
        })

    def post(self, demand_id):
        """
        create by ld_wu 2017-11-22
        反馈失效
        :return:
        """
        # 不是工程商则直接返回
        if self.user.type != UserType.ENGINEER.value:
            return Response(NOT_ENGINEER)

        data, errors = feedback_schema.load(request.get_json())
        if errors:
            return Response(
                PARAMS_ILLEGAL.new_format_msg(list(errors.values())[0][0])
            )
        partic = Partic.get_by_args(
            demand_id=demand_id,
            supplier_id=self.user.id,
            status=ParticStatus.SUCCEED.value
        )
        if not partic:
            return Response(NO_PARTIC)

        if partic.feedback:
            return Response(FEEDBACK_DUPLICATE)

        if partic.follow_type == FollowType.LOCKED.value:
            return Response(LOCKED_CANNOT_FEEDBACK)

        now = get_timestamp()
        feedback_type = data.get('feedback_type')
        feedback = data.get('feedback')

        if feedback_type:
            if partic.follow_type != FollowType.KEEP.value:
                return Response(MUST_BE_KEEP)

            if self.user.pack.bid_point < LOCKED_PAID_BID_POINT:
                return Response(LOW_BID_POINT)

            demand = Demand.get_by_id(partic.demand_id)
            # 判断订单是否已被锁定
            if demand.is_locked:
                return Response(DEMAND_LOCKED)
            if demand.status == DemandStatus.DONE.value:
                return Response(DEMAND_DONE)
            # 扣除锁定汇币
            self.user.pack.bid_point -= LOCKED_PAID_BID_POINT
            BidCostRecord.create(
                user_id=self.user.id,
                cost_type=BidCostType.LOCKED.value,
                bid_point=LOCKED_PAID_BID_POINT,
                left_bid_point=self.user.pack.bid_point,
                demand_id=partic.demand_id
            )

            # 日统计
            current_app.zone_stat_exchanger.exchange_send(json.dumps({
                'locked_cnt': 1,
                'cost_bid_point': LOCKED_PAID_BID_POINT,
                'cost_bid_user_id': self.user.id,
                'war_zone_id': self.user.war_zone_id
            }))
            current_app.common_producer.send(
                json.dumps(
                    dict(
                        backend='bid_stock_stat',
                        func='day_stat',
                        kwargs=dict(
                            stat_type='cost_bid_point',
                            stat_cnt=LOCKED_PAID_BID_POINT,
                        )
                    )
                )
            )

            # 修改跟进状态
            partic.follow_type = FollowType.LOCKED.value
            partic.feedback_at = now
            partic.feedback_status = FeedbackStatus.PASS.value

            demand.status = DemandStatus.COMPETE_DONE.value

            # 判断是否需要退返派单点
            current_app.common_producer.send(
                json.dumps(
                    dict(
                        backend='demand_backend',
                        func='lock_handle',
                        kwargs=dict(
                            demand_id=demand.id,
                        )
                    )
                )
            )
        else:
            # 派单不允许反馈
            if partic.demand_status in (
                DemandStatus.DISPATCHING.value, DemandStatus.MASTER_DISPATCHING.value
            ):
                return Response(DISPATCH_CANNOT_FOLLOW)

            # 超过1天不允许发起反馈
            if now - partic.bid_win_at >= 60 * 60 * 24:
                return Response(ONE_DAY_DISABLE_FEEDBACK)
            if partic.apply_real_number_state == ApplyRealNumberState.GET_BEFORE.value:
                return Response(APPLY_REAL_NUM_ALREADY)
            if partic.voucher_id != 0:
                return Response(VOUCHER_PARTIC_CANNOT_FEEDBACK)

            # 无理由
            if feedback == Feedback.NO_REASON.value:
                user_info = UserInfo.default_user_info(self.user.id)
                # 判断用户是否仍有无理由发起次数
                if user_info.free_reason_feedback_cnt <= 0:
                    return Response(OUT_OF_FREE_REASON_CNT)
                # 直接通过, 并减一次无理由次数
                user_info.free_reason_feedback_cnt -= 1
                partic.feedback_at = now
                partic.feedback = feedback
                partic.feedback_content = data.get('feedback_content', '')
                partic.feedback_status = FeedbackStatus.PASS.value
                partic.feedback_pass()
            else:
                # 有理由
                if self.user.reason_left_times <= 0:
                    return Response(OUT_OF_REASON_CNT)

                partic.feedback_at = now
                partic.feedback = feedback
                partic.feedback_content = data.get('feedback_content', '')
                partic.feedback_status = (
                    partic.feedback_status or FeedbackStatus.PENDING.value
                )

        return Response(
            SUCCESS,
            dict(
                my_offer=int(partic.offer),
                # punish_bid_point=punish_bid_point,
                feedback_status=partic.feedback_status
            )
        )


@api.resource('/demand/<int:demand_id>/finish_img')
class DemandFinishImgResource(AppAuthResource):
    """
    create by ld_wu 2017-11-22
    项目申报完成
    """

    def post(self, demand_id):
        # 不是工程商则直接返回
        if self.user.type != UserType.ENGINEER.value:
            return Response(NOT_ENGINEER)

        data, errors = demand_finish_img_schema.load(request.get_json())
        if errors:
            return Response(
                PARAMS_ILLEGAL.new_format_msg(list(errors.values())[0][0])
            )
        try:
            im = Image.open(BytesIO(base64.b64decode(data['img'])))
        except (binascii.Error, OSError):
            return Response(IMAGE_TYPE_ERROR)

        partic = Partic.get_by_args(
            demand_id=demand_id,
            supplier_id=self.user.id
        )
        if not partic:
            return Response(NO_PARTIC)

        if partic.status != ParticStatus.SUCCEED.value:
            return Response(NOT_WIN_DEMAND)

        path = os.path.join(
            os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
            'api/img/demand_finish_img'
        )

        tmp_filename = str(demand_id) + '_' + str(self.user.id)

        filename = '{filename}.{format}'.format(
            filename=tmp_filename,
            format=im.format.lower()
        )

        im.save(
            '{path}/{filename}'.format(
                path=path,
                filename=filename
            ),
            im.format
        )

        new_demand_finish_img = DemandFinishImg.create(
            user_id=self.user.id,
            demand_id=demand_id,
            img='demand_finish_img/{filename}'.format(filename=filename)
        )

        return Response(
            SUCCESS,
            body=dict(img=new_demand_finish_img.img_url)
        )


@api.resource('/demand/<int:demand_id>/bid')
class DemandBidResource(AppAuthResource):
    """
    create by ld_wu 2017-11-22
    项目竞标
    """

    def post(self, demand_id):
        # 不是工程商则直接返回
        if self.user.type != UserType.ENGINEER.value:
            return Response(NOT_ENGINEER)
        # 拉黑用户不允许竞价
        if self.user.black:
            return Response(USER_BLACK)

        data, errors = demand_bid_schema.load(request.get_json())
        if errors:
            return Response(
                PARAMS_ILLEGAL.new_format_msg(list(errors.values())[0][0]))
        demand = Demand.get_by_id(demand_id)
        if not demand:
            return Response(DEMAND_NOT_EXIST)

        if demand.is_locked:
            return Response(DEMAND_LOCKED)

        # 签署用户协议
        protocol_version = current_app.redis.get(PROTOCOL_VERSION_KEY) or ''
        if protocol_version and not UserProtocol.get_by_args(
                user_id=self.user.id, version=protocol_version
        ):
            protocol = UserProtocol.create(
                user_id=self.user.id,
                mobile=self.user.mobile,
                nickname=self.user.nickname,
                version=protocol_version
            )
            # FIXME by chenhj at 2017/10/30 产品要求: 当该用户的创建时间在2017-10-18(用户协议版本上线时间)之前的话,
            #  则用户协议表的创建时间同步为该用户的创建时间, 否则为当前时间, 2017-10-18的时间戳为1508256000
            if self.user.created_at <= 1508256000:
                protocol.created_at = self.user.created_at
                protocol.updated_at = self.user.created_at

        dispatch_partic = Partic.query.filter(
            Partic.demand_id == demand.id,
            Partic.supplier_id == self.user.id,
            Partic.demand_status.in_([
                DemandStatus.DISPATCHING.value,
                DemandStatus.MASTER_DISPATCHING.value
            ])
        ).first()

        # 派单用户在圆满完成前均可抢单
        if dispatch_partic:
            if dispatch_partic.status == ParticStatus.SUCCEED.value:
                return Response(BID_ALREADY)

            if demand.status in (
                DemandStatus.DRAFT.value, DemandStatus.UNCHECKED.value,
                DemandStatus.DELAY.value, DemandStatus.DONE.value,
                DemandStatus.INVALID.value
            ):
                return Response(DEMAND_NOT_ONGOING)

            # 判断该企业下是否已有派单抢单成功用户
            if self.user.highest_level != EnterpriseLevel.LEVEL_6.value:
                # 非地方合作伙伴, 取同企业下中标人
                enterprise_cond = (Partic.enterprise_id == dispatch_partic.enterprise_id)
            else:
                # 地方合作伙伴, 取所有地方合作伙伴企业下中标人
                enterprise_cond = (Partic.enterprise_level == EnterpriseLevel.LEVEL_6.value)
            winner = Partic.query.filter(
                Partic.demand_id == demand.id,
                Partic.published_count == demand.published_count,
                Partic.demand_status.in_([
                    DemandStatus.DISPATCHING.value,
                    DemandStatus.MASTER_DISPATCHING.value
                ]),
                Partic.status == ParticStatus.SUCCEED.value,
                enterprise_cond
            ).first()
            if winner:
                return Response(
                    DISPATCH_ALREADY_BID_BY_COLLEAGUE,
                    {
                        'winner': {
                            'nickname': winner.owner.nickname,
                            'mobile': '****' + winner.owner.mobile[-4:]
                        }
                    }
                )

            # 入winner_consumer
            current_app.winner_producer.send(
                json.dumps(dict(
                    func='dispatch_assign_winner',
                    args=(),
                    kwargs=dict(
                        partic_id=dispatch_partic.id,
                        demand_id=demand.id,
                    )
                ))
            )
            return Response(SUCCESS, {
                'demand_info': generate_demand_info_json(demand, self.user)
            })
        else:
            if demand.status not in (
                DemandStatus.BRONZE_MEDAL_DISCOUNT.value,
                DemandStatus.ONGOING.value,
                DemandStatus.FREE_ONGOING.value,
                DemandStatus.SECOND_ONGOING.value
            ):
                return Response(DEMAND_NOT_ONGOING)

        offer = data['offer']

        if demand.status == DemandStatus.BRONZE_MEDAL_DISCOUNT.value:
            # 判断用户是否为铜牌
            if not self.user.is_bronze_medal:
                return Response(USER_NOT_BRONZE)

            # 判断用户铜牌是否到期
            if not self.user.bronze_medal_valid:
                return Response(USER_BRONZE_INVALID)

            # 判断offer是否等于期望成交价
            target_offer = calc_bronze_bid_point(demand)
            if target_offer != offer:
                return Response(OFFER_NOT_EQ_EXPECTED)

        # 免费竞价出价必须为0
        if demand.status == DemandStatus.FREE_ONGOING.value:
            if offer != 0:
                return Response(NOT_MATCH_FREE_OFFER)

        # 二次竞价的出价固定为8
        if demand.status == DemandStatus.SECOND_ONGOING.value:
            price = get_demand_price(demand)
            if offer != price:
                return Response(NOT_MATCH_SECOND_OFFER.new_format_msg(price))

        win_partic = Partic.query.filter(
            Partic.demand_id == demand.id,
            Partic.supplier_id == self.user.id,
            Partic.status.in_([ParticStatus.SUCCEED.value, ParticStatus.TIMEOUT_NOT_CONTACT.value])
        ).first()

        if win_partic:
            # 清除缓存partic信息
            Partic.clear_demand_partic_cache_data(current_app.redis, demand.id)
            return Response(BID_ALREADY)
        if not self.user.pack:
            return Response(NO_PACK)

        # 剩余汇币或者可用的最大代金券额
        max_bid_point = max(
            Voucher.max_voucher_bid_point(self.user.id),
            self.user.pack.bid_point
        )

        if max_bid_point < offer:
            return Response(OUT_OF_PACK)

        # 爆灯不判断是否高于最高出价
        if offer < MAX_OFFER:
            # 进行中的项目报价不可低于起拍点
            if (demand.status == DemandStatus.ONGOING.value and
                    offer < demand.bid_start_point):
                return Response(BID_LOWER_THAN_START)

            # 若提供的报价低于项目已有的最高报价, 则报价失败(仅当项目在首次竞价中时判断)
            if (demand.status == DemandStatus.ONGOING.value and
                    offer <= demand.max_partic_offer):
                # 清除缓存partic信息
                Partic.clear_demand_partic_cache_data(current_app.redis, demand.id)
                return Response(BID_LOWER)
        else:
            # 爆灯修改出价为爆灯价
            offer = MAX_OFFER

        partic = Partic.get_by_args(
            demand_id=demand.id,
            supplier_id=self.user.id,
            published_count=demand.published_count,
        )
        # 出价需高于自身出价(仅首次竞价的项目需要判断)
        if (
            demand.status == DemandStatus.ONGOING.value
            and offer < MAX_OFFER
            and partic and offer <= partic.offer
        ):
            return Response(BID_LOWER_THAN_SELF)

        if partic:
            partic.status = ParticStatus.WAITING.value
            partic.offer = offer
            partic.offer_at = int(time.time())
            partic.demand_status = demand.status
            partic.is_training = False
            partic.is_revert = False
        else:
            # 写入抢单表, 并记录竞价点
            partic = Partic.create(
                demand_id=demand.id,
                supplier_id=self.user.id,
                status=ParticStatus.WAITING.value,
                offer=offer,
                offer_at=int(time.time()),
                demand_status=demand.status,
                credit=self.user.credit,
                published_count=demand.published_count
            )
            demand.bid_cnt += 1
            # 只有首次出价的进行记录
            current_app.zone_stat_exchanger.exchange_send(json.dumps({
                'bid_cnt': 1,
                'bid_user_id': self.user.id,
                'war_zone_id': self.user.war_zone_id
            }))

            if demand.status == DemandStatus.ONGOING.value:
                self.user.credit += UserCreditRule.ONGOING_OFFER.value

        try:
            db.session.commit()
            # TO1, TO2,政府订单, 无中标流程
            if demand.tag not in (
                DemandTag.VIRT_ONE.value,
                DemandTag.VIRT_TWO.value,
                DemandTag.GOV_RO.value
            ):
                if demand.status in (
                    DemandStatus.FREE_ONGOING.value,
                    DemandStatus.SECOND_ONGOING.value
                ):
                    current_app.winner_producer.send(
                        json.dumps(dict(
                            func='assign_winner',
                            args=(),
                            kwargs=dict(
                                partic_ids=[partic.id],
                                demand_id=demand_id,
                                published_count=demand.published_count
                            )
                        ))
                    )

                if demand.status == DemandStatus.BRONZE_MEDAL_DISCOUNT.value:
                    current_app.winner_producer.send(
                        json.dumps(dict(
                            func='bronze_assign_winner',
                            args=(),
                            kwargs=dict(
                                partic_id=partic.id,
                                demand_id=demand_id,
                                published_count=demand.published_count
                            )
                        ))
                    )
        except:
            import traceback
            print(traceback.format_exc())
            db.session.rollback()

        # 清除缓存partic信息
        Partic.clear_demand_partic_cache_data(current_app.redis, demand.id)

        return Response(SUCCESS, {
            'demand_info': generate_demand_info_json(demand, self.user)
        })


@api.resource('/demands/today/counts')
class DemandTodayCntResource(Resource):
    """
    create by ld_wu 2017-11-24
    查询今日我参与的和所有的项目数目
    ref: https://tower.im/projects/47b71f052c8441eba37547784ba957c3/docs/8241a9ab75af4a16af00ec2843c63a28/
    """

    def get(self):
        token = request.headers.get('token')
        # 如果是工程商登录, 则参与状态改为该工程商对此项目的参与状态
        user = AuthToken.verify_app_token(token) if token else None

        me_cnt = 0
        if user:
            if not user.pack:
                area_id = [user.area_id]
            else:
                area_id = [i.id for i in user.attention_areas]
            me_cnt = Demand.get_today_active_demands_count_by_area(area_id)

        return Response(
            body=dict(
                me_cnt=me_cnt,
                total_cnt=RandomDemandCount().demand_count()
            )
        )


@api.resource('/demand/<int:demand_id>/scan')
class DemandScanAppResource(Resource):
    """
    created by chenhj at 2018/4/17

    """
    def put(self, demand_id):
        """
        created by chenhj at 2018/4/17

        """
        demand = Demand.get_by_id(demand_id)
        if not demand:
            return Response(DEMAND_NOT_EXIST)
        demand.scan_count += 1

        # add by zhouhl 2018-05-07
        # 记录用户访问次数
        token = request.headers.get('token')
        user = AuthToken.verify_app_token(token) if token else None

        if user:
            UserVisitDemand.visit_counter(
                user.id, demand.id, UserVisitDemandChannel.APP.value
            )
        return Response()


@api.resource('/demand/<int:demand_id>/apply_real_number')
class SupplierApplyRealNumberResource(AppAuthResource):
    """
    created by chenhj at 2018/6/27
    申请获取用户真实号码接口
    """
    def put(self, demand_id):
        """
        created by chenhj at 2018/6/27

        """
        if self.user.type != UserType.ENGINEER.value:
            return Response(NOT_ENGINEER)

        demand = Demand.get_by_id(demand_id)
        if not demand:
            return Response(DEMAND_NOT_EXIST)

        partic = Partic.get_by_args(
            demand_id=demand.id,
            supplier_id=self.user.id,
            status=ParticStatus.SUCCEED.value
        )
        if not partic:
            return Response(NO_PARTIC)

        # 放弃跟进后不允许申请真实号码
        if partic.follow_type == FollowType.GIVE_UP.value:
            return Response(GIVE_UP_CANNOT_APPLY_REAL_NUM)

        if partic.apply_real_number_state != ApplyRealNumberState.NA.value:
            return Response(APPLY_REAL_NUM_ALREADY)

        # 不允许拨打前获取
        if partic.contact_state != ContactState.YES.value:
            return Response(CAN_NOT_GET_BEFORE)

        partic.apply_real_number_state = ApplyRealNumberState.GET_AFTER.value

        # 记录申请真实号码时间
        partic.apply_real_number_at = get_timestamp()

        demand_client = User.get_by_id(demand.uid)

        db.session.commit()
        Partic.clear_demand_partic_cache_data(current_app.redis, demand.id)

        return Response(SUCCESS, {
            'real_number': demand_client.mobile,
            'apply_real_number_state': partic.apply_real_number_state
        })


@api.resource('/enterprise/dispatch_demands')
class EnterpriseDispatchDemandsResource(AppAuthResource):
    """
    created by chenhj at 2018/8/8

    """
    def get(self):
        """
        created by chenhj at 2018/8/8

        """
        if self.user.type != UserType.ENGINEER.value:
            return Response(NOT_ENGINEER)

        if not self.user.enterprise_info:
            return Response(NOT_ENTERPRISE_USER)

        enterprise_id = self.user.enterprise_info

        data, errors = enterprise_dispatch_in_schema.load(request.args)
        if errors:
            return Response(
                PARAMS_ILLEGAL.new_format_msg(list(errors.values())[0][0])
            )

        query_type = data['query_type']
        today = date.today()
        if query_type == EnterpriseDispatchQueryType.THIS_WEEK:
            start = today - timedelta(days=today.weekday())
            end = today + timedelta(days=1)
        elif query_type == EnterpriseDispatchQueryType.THIS_MONTH:
            start = date(today.year, today.month, 1)
            end = today + timedelta(days=1)
        elif query_type == EnterpriseDispatchQueryType.LAST_MONTH:
            last_month = today - timedelta(days=31)
            start = date(last_month.year, last_month.month, 1)
            end = date(last_month.year, today.month, 1)
        else:
            # 自定义
            if 'self_month' not in data:
                return Response(
                    PARAMS_ILLEGAL.new_format_msg('自定义月份不合法')
                )
            self_month = data['self_month']
            if self_month == 99:
                start = end = None
            else:
                start = date(today.year, self_month, 1)
                next_month = start + timedelta(days=31)
                end = date(today.year, next_month.month, 1)

        if start and end:
            start = start.strftime('%s')
            end = end.strftime('%s')
            cond = and_(
                Partic.enterprise_id == enterprise_id,
                Partic.created_at.between(start, end)
            )
        else:
            cond = and_(Partic.enterprise_id == enterprise_id)

        ent = Enterprise.get_by_id(enterprise_id)
        # 派单总量
        dispatch_demands_ids = Partic.query.filter(
            cond
        ).with_entities(
            Partic.demand_id.distinct()
        )
        dispatch_demands_cnt = dispatch_demands_ids.count()
        # 跟进总量
        dispatch_follow_cnt = Partic.query.filter(
            cond,
            Partic.follow_type.in_([FollowType.KEEP.value, FollowType.LOCKED.value])
        ).with_entities(
            Partic.demand_id.distinct()
        ).count()
        # 派单剩余容量
        left_dispatch_cnt = ent.leave_dispatch_cnt
        # 到期日期
        expired_date = ent.expired_date.strftime('%F')

        demands = Demand.query.filter(
            Demand.id.in_(dispatch_demands_ids)
        ).order_by(Demand.published_at.desc())

        sort_val = data.get('sort_value', 0)
        if sort_val:
            demands = demands.filter(
                Demand.published_at < sort_val
            )
        demands = demands.limit(20)

        demand_list = enterprise_dispatch_out_schema.dump(demands)[0]

        for each_demand in demand_list:
            # 是否退单
            each_demand['is_revert'] = False
            # 跟进人
            each_demand['follower'] = None
            # 当前抢单成功人数
            each_demand['success_bid_count'] = demand_bid_count(
                each_demand['id'],
                each_demand['published_count'],
                db.session
            )
            demand_partic = Partic.query.filter(
                Partic.demand_id == each_demand['id'],
                Partic.enterprise_id == enterprise_id
            ).order_by(
                Partic.status.desc()
            ).first()
            if demand_partic:
                if demand_partic.status == ParticStatus.SUCCEED.value:
                    each_demand['follower'] = demand_partic.owner.nickname
                # 若是退单的, 则更新is_revert值
                each_demand['is_revert'] = demand_partic.is_revert

        return Response(body=dict(
            dispatch_demands_cnt=dispatch_demands_cnt,
            dispatch_follow_cnt=dispatch_follow_cnt,
            left_dispatch_cnt=left_dispatch_cnt,
            expired_date=expired_date,
            demand_list=demand_list
        ))

    def put(self):
        """
        created by chenhj at 2018/8/13

        """
        if self.user.type != UserType.ENGINEER.value:
            return Response(NOT_ENGINEER)

        if not self.user.enterprise_info:
            return Response(NOT_ENTERPRISE_USER)

        enterprise_id = self.user.enterprise_info

        data, errors = enterprise_dispatch_export_schema.load(request.get_json())
        if errors:
            return Response(
                PARAMS_ILLEGAL.new_format_msg(list(errors.values())[0][0])
            )

        query_type = data['query_type']
        today = date.today()
        if query_type == EnterpriseDispatchQueryType.THIS_WEEK:
            start = today - timedelta(days=today.weekday())
            end = today + timedelta(days=1)
        elif query_type == EnterpriseDispatchQueryType.THIS_MONTH:
            start = date(today.year, today.month, 1)
            end = today + timedelta(days=1)
        elif query_type == EnterpriseDispatchQueryType.LAST_MONTH:
            last_month = today - timedelta(days=31)
            start = date(last_month.year, last_month.month, 1)
            end = date(last_month.year, today.month, 1)
        else:
            # 自定义
            if 'self_month' not in data:
                return Response(
                    PARAMS_ILLEGAL.new_format_msg('自定义月份不合法')
                )
            self_month = data['self_month']
            if self_month == 99:
                start = end = None
            else:
                start = date(today.year, self_month, 1)
                next_month = start + timedelta(days=31)
                end = date(today.year, next_month.month, 1)

        if start and end:
            start = start.strftime('%s')
            end = end.strftime('%s')
        current_app.common_producer.send(
            json.dumps(
                dict(
                    backend='demand_backend',
                    func='dispatch_demands_export',
                    kwargs=dict(
                        enterprise_id=enterprise_id,
                        start=start,
                        end=end,
                        email=data['email'],
                        nickname=self.user.nickname
                    )
                )
            )
        )
        return Response()
